﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using CodeHighlighter.Model;
using GalaSoft.MvvmLight;

namespace CodeHighlighter.ViewModel
{
    /// <summary>
    /// This class contains properties that the main View can data bind to.
    /// <para>
    /// Use the <strong>mvvminpc</strong> snippet to add bindable properties to this ViewModel.
    /// </para>
    /// <para>
    /// You can also use Blend to data bind with the tool's support.
    /// </para>
    /// <para>
    /// See http://www.galasoft.ch/mvvm/getstarted
    /// </para>
    /// </summary>
    public class MainViewModel : ViewModelBase
    {
        /// <summary>
        /// The <see cref="CodeText" /> property's name.
        /// </summary>
        public const string CodeTextPropertyName = "CodeText";

        private string _codeText;

        /// <summary>
        /// Gets the CodeText property.
        /// TODO Update documentation:
        /// Changes to that property's value raise the PropertyChanged event. 
        /// This property's value is broadcasted by the Messenger's default instance when it changes.
        /// </summary>
        public string CodeText
        {
            get
            {
                return _codeText;
            }

            set
            {
                if (_codeText == value)
                {
                    return;
                }

                var oldValue = _codeText;
                _codeText = value;

                CodeUpdater();

                RaisePropertyChanged(CodeTextPropertyName);
            }
        }

        public ObservableCollection<CodeLine> CodeLines { get; set; }

        public string Welcome
        {
            get
            {
                return "Welcome to MVVM Light";
            }
        }

        /// <summary>
        /// Initializes a new instance of the MainViewModel class.
        /// </summary>
        public MainViewModel()
        {
            if (IsInDesignMode)
            {
                // Code runs in Blend --> create design time data.
            }
            else
            {
                // Code runs "for real"
                CodeLines = new ObservableCollection<CodeLine>();
                //CodeText = new StreamReader(@"DesignData/sample.txt").ReadToEnd();
            }
        }

        public void CodeUpdater()
        {
            var lines = CodeText.Split('\n', '\r');

            // find the changed lines by excluding the first and last lines that have not changed
            int startline = 0;
            while (startline < CodeLines.Count && startline < lines.Length
                            && CodeLines[startline].Text == lines[startline].PadRight(1, ' '))
            {
                startline++;
            }

            int stopline = 0;
            while (stopline < CodeLines.Count && stopline < lines.Length
                            && CodeLines.Count - 1 - stopline > startline && lines.Length - 1 - stopline > startline
                            && CodeLines[CodeLines.Count - 1 - stopline].Text == lines[lines.Length - 1 - stopline].PadRight(1, ' '))
            {
                stopline++;
            }

            // build CodeLine objects for the changed lines
            List<CodeLine> changedLines = BuildChangedLines(lines, startline, stopline);

            // remove exisiting changed lines and add the new ones
            var toRemove = CodeLines.Count - stopline;
            for (int i = startline; i < toRemove; i++)
            {
                CodeLines.RemoveAt(startline);
            }
            for (int i = 0; i < changedLines.Count; i++)
            {
                CodeLines.Insert(i + startline, changedLines[i]);
            }
        }

        private List<CodeLine> BuildChangedLines(string[] lines, int startline, int stopline)
        {
            List<CodeLine> changedLines = new List<CodeLine>();

            for (int i = startline; i < lines.Length - stopline; i++)
            {
                var line = lines[i];
                var codeLine = new CodeLine();
                changedLines.Add(codeLine);

                if (line == string.Empty)
                {
                    codeLine.AddCodeWord(" "); // add a space to ensure the line is displayed even if it's empty
                    continue;
                }

                //var words = line.Split(WordDelimiters);
                //int splitpos = 0;
                //foreach (var word in words)
                //{
                //    splitpos += word.Length;
                //    codeLine.AddCodeWord(word);
                //    if (splitpos < line.Length)
                //        codeLine.AddCodeWord(line[splitpos].ToString());
                //    splitpos++;
                //}

                var words = SplitForWords(line);
                foreach (var word in words)
                {
                    codeLine.AddCodeWord(word);
                }
            }

            return changedLines;
        }

        private static List<string> SplitForWords(string line)
        {
            List<string> words = new List<string>();

            while (line != "")
            {
                var allMatches = new List<Match>
                                     {
                                         CodeLine.Comment.Match(line),
                                         CodeLine.Bracket.Match(line),
                                         CodeLine.InlineMath.Match(line),
                                         CodeLine.KeyWord.Match(line)
                                     };

                if ((allMatches.Where(match => match.Success == true)).Count() == 0)
                {
                    words.Add(line);
                    return words;
                }
                else
                {
                    Match firstMatch = (from match in allMatches
                                        where match.Success == true
                                        orderby match.Index
                                        select match).First();

                    if (firstMatch.Index != 0)
                    {
                        words.Add(line.Substring(0, firstMatch.Index));
                    }

                    words.Add(firstMatch.Value);

                    line = line.Substring(firstMatch.Index + firstMatch.Length);
                }
            }

            return words;
        }

        ////public override void Cleanup()
        ////{
        ////    // Clean up if needed

        ////    base.Cleanup();
        ////}
    }
}